/*************
<ð.ð watcher ð.ð>
**************/

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/cdev.h>

MODULE_LICENSE("|×.×| All rights reserved. Copyright® 2010 |×.×|");
MODULE_AUTHOR("David Castaño & Guillermo Muñiz");
MODULE_DESCRIPTION("ProSO monitoring module");

#define NUM_OPS 5
/*operaciones auditadas*/
#define LOPEN 0
#define LCLOSE 1
#define LWRITE 2
#define LLSEEK 3
#define LCLONE 4

/*operaciones auditadas*/
#define POS_SYSCALL_OPEN 5
#define POS_SYSCALL_CLOSE 6
#define POS_SYSCALL_WRITE 4
#define POS_SYSCALL_LSEEK 19
#define POS_SYSCALL_CLONE 120

#define proso_rdtsc(low,high) \
__asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))

static inline unsigned long long proso_get_cycles(void) {
	unsigned long eax, edx;

	proso_rdtsc(eax,edx);

	return ((unsigned long long) edx << 32) + eax;
} 

extern void* sys_call_table[];
void* sct_old[NUM_OPS];
void* sct_local[NUM_OPS];

char* nombre_op[NUM_OPS] = {"OPEN","CLOSE","WRITE","LSEEK","CLONE"};

int translate[NUM_OPS] = {POS_SYSCALL_OPEN,POS_SYSCALL_CLOSE,POS_SYSCALL_WRITE,POS_SYSCALL_LSEEK,POS_SYSCALL_CLONE};

struct estadisticas {
	int num_entrades;
	int num_sortides_ok;
	int num_sortides_error;
	unsigned long long durada_total;
};

struct ops_stats {
	int my_pid;
	struct estadisticas est[NUM_OPS];
};

struct my_thread_info {
	struct thread_info ti;
	struct ops_stats os;
};

/*
int pid;
module_param(pid, int, 0);
MODULE_PARM_DESC(pid, "Process identifier to monitor (default is pid of current process)");
*/

inline struct ops_stats* get_estats(void){
  return &((struct my_thread_info*)current_thread_info())->os;
}
/*
void print_stats(void) {
    struct task_struct* task;
    struct estadisticas* est;
    int i;

    task = find_task_by_pid(pid);

    if (task == NULL) printk(KERN_DEBUG "ERROR: No se pueden mostrar las estadisticas del proceso %d, no existe.\n", pid);
    else {
        est = ((struct my_thread_info*)task->thread_info)->os.est;
        for (i = 0; i < NUM_OPS; ++i) {
            printk(KERN_DEBUG "Llamadas totales de %s: %d\n", nombre_op[i], est[i].num_entrades);
            printk(KERN_DEBUG "Llamadas correctas: %d\n", est[i].num_sortides_ok);
            printk(KERN_DEBUG "Llamadas incorrectas: %d\n", est[i].num_sortides_error);
            printk(KERN_DEBUG "Tiempo total: %lld\n\n", est[i].durada_total);
        }
    }
}
*/

void init_est(struct ops_stats* os){
	int i;
	struct estadisticas * est = os->est;
	os->my_pid = current->pid;
	for(i = 0; i < NUM_OPS; ++i){
		est[i].num_entrades = est[i].num_sortides_ok = est[i].num_sortides_error = 0;
		est[i].durada_total = 0;
	}
	
}

void reset_est(int pid) {
	struct task_struct * task;
	task = find_task_by_pid(pid);
	init_est(&((struct my_thread_info*)task->thread_info)->os);
}
EXPORT_SYMBOL(reset_est);
void reset_est_all(void){
	struct task_struct * task;
	for_each_process(task) {
		init_est(&((struct my_thread_info*)task->thread_info)->os);
	}
}
EXPORT_SYMBOL(reset_est_all);
void comprueba_pid(void){
	struct ops_stats* os = get_estats();
	if(os->my_pid != current->pid) init_est(os);
}


long sys_open_local(const char __user * filename, int flags, int mode){
	unsigned long long tiempo;
	long res;	
	struct ops_stats* os = get_estats();
	struct estadisticas* estats = os->est;	

	try_module_get(THIS_MODULE);

	comprueba_pid();
	estats[LOPEN].num_entrades++;
	tiempo = proso_get_cycles();
	res = ((long (*)(const char __user *, int, int))sct_old[LOPEN])(filename,flags,mode);
	tiempo = proso_get_cycles() - tiempo;
	estats[LOPEN].durada_total += tiempo;
	if(res < 0)estats[LOPEN].num_sortides_error++;
	else estats[LOPEN].num_sortides_ok++;

	module_put(THIS_MODULE);

	//printk(KERN_DEBUG "open\n");
	return res;
}

long sys_close_local(unsigned int fd){
	unsigned long long tiempo;
	long res;	
	struct ops_stats* os = get_estats();
	struct estadisticas* estats = os->est;	

	try_module_get(THIS_MODULE);

	comprueba_pid();
	estats[LCLOSE].num_entrades++;
	tiempo = proso_get_cycles();
	res = ((long (*)(unsigned int))sct_old[LCLOSE])(fd);
	tiempo = proso_get_cycles() - tiempo;
	estats[LCLOSE].durada_total += tiempo;
	if(res < 0)estats[LCLOSE].num_sortides_error++;
	else estats[LCLOSE].num_sortides_ok++;

	module_put(THIS_MODULE);

	//printk(KERN_DEBUG "close\n");
	return res;
}

ssize_t sys_write_local(unsigned int fd, const char __user * buf, size_t count){
	unsigned long long tiempo;
	ssize_t res;	
	struct ops_stats* os = get_estats();
	struct estadisticas* estats = os->est;	

	try_module_get(THIS_MODULE);

	comprueba_pid();
	estats[LWRITE].num_entrades++;
	tiempo = proso_get_cycles();
	res = ((ssize_t (*)(unsigned int, const char __user*,size_t))sct_old[LWRITE])(fd,buf,count);
	tiempo = proso_get_cycles() - tiempo;
	estats[LWRITE].durada_total += tiempo;
	if(res < 0)estats[LWRITE].num_sortides_error++;
	else estats[LWRITE].num_sortides_ok++;

	module_put(THIS_MODULE);

	//printk(KERN_DEBUG "write\n");
	return res;
}

off_t sys_lseek_local(unsigned int fd, off_t offset, unsigned int origin){
	unsigned long long tiempo;
	off_t res;	
	struct ops_stats* os = get_estats();
	struct estadisticas* estats = os->est;

	try_module_get(THIS_MODULE);

	comprueba_pid();
	estats[LLSEEK].num_entrades++;
	tiempo = proso_get_cycles();
	res = ((off_t (*)(unsigned int ,off_t,unsigned int))sct_old[LLSEEK])(fd,offset,origin);
	tiempo = proso_get_cycles() - tiempo;
	estats[LLSEEK].durada_total += tiempo;
	if(res < 0)estats[LLSEEK].num_sortides_error++;
	else estats[LLSEEK].num_sortides_ok++;

	module_put(THIS_MODULE);

	//printk(KERN_DEBUG "lseek\n");
	return res;
}

int sys_clone_local(struct pt_regs regs){
	unsigned long long tiempo;
	int res;	
	struct ops_stats* os = get_estats();
	struct estadisticas* estats = os->est;	

	try_module_get(THIS_MODULE);

	comprueba_pid();
	estats[LCLONE].num_entrades++;
	tiempo = proso_get_cycles();
	res = ((int (*)(struct pt_regs))sct_old[LCLONE])(regs);
	tiempo = proso_get_cycles() - tiempo;
	estats[LCLONE].durada_total += tiempo;
	if(res < 0)estats[LCLONE].num_sortides_error++;
	else estats[LCLONE].num_sortides_ok++;

	module_put(THIS_MODULE);

	//printk(KERN_DEBUG "clone\n");
	return res;
}



void activa_watcher(int op){
	if (op < 0) {
		sys_call_table[POS_SYSCALL_OPEN] = sct_local[LOPEN];
		sys_call_table[POS_SYSCALL_CLOSE] = sct_local[LCLOSE];
		sys_call_table[POS_SYSCALL_WRITE] = sct_local[LWRITE];
		sys_call_table[POS_SYSCALL_LSEEK] = sct_local[LLSEEK];
		sys_call_table[POS_SYSCALL_CLONE] = sct_local[LCLONE];
	}
	else sys_call_table[translate[op]] = sct_local[op];
}

EXPORT_SYMBOL(activa_watcher);

void init_table(void) {

	sct_local[LOPEN] = (void*)sys_open_local;
	sct_local[LCLOSE] = (void*)sys_close_local;
	sct_local[LWRITE] = (void*)sys_write_local;
	sct_local[LLSEEK] = (void*)sys_lseek_local;
	sct_local[LCLONE] = (void*)sys_clone_local;

	/*guardamos las llamadas a sistema originales*/
	sct_old[LOPEN] = sys_call_table[POS_SYSCALL_OPEN];
	sct_old[LCLOSE] = sys_call_table[POS_SYSCALL_CLOSE];
	sct_old[LWRITE] = sys_call_table[POS_SYSCALL_WRITE]; 
	sct_old[LLSEEK] = sys_call_table[POS_SYSCALL_LSEEK];
	sct_old[LCLONE] = sys_call_table[POS_SYSCALL_CLONE];
	/*asignamos nuestras llamadas a sistema*/
	activa_watcher(-1);
}


/*
* Inicialització del modul.
*/
static int __init watcher_init(void)
{   /* Codi d’inicialització */

	printk(KERN_DEBUG "Watcher carregat amb exit\n");

	//if (!pid) pid = current->pid;
	
	init_table();
	return 0;
	// Aquesta funció retorna 0 si tot ha
	// anat correctament i < 0 en cas d’error

}



void desactiva_watcher(int op){
	if (op < 0) {
		sys_call_table[POS_SYSCALL_OPEN] = sct_old[LOPEN];
		sys_call_table[POS_SYSCALL_CLOSE] = sct_old[LCLOSE];
		sys_call_table[POS_SYSCALL_WRITE] = sct_old[LWRITE]; 
		sys_call_table[POS_SYSCALL_LSEEK] = sct_old[LLSEEK];
		sys_call_table[POS_SYSCALL_CLONE] = sct_old[LCLONE];
	}
	else sys_call_table[translate[op]] = sct_old[op];

}
EXPORT_SYMBOL(desactiva_watcher);


/*
* Descarrega el módul.
*/
static void __exit watcher_exit(void)
{
/* Codi de finalització */
	desactiva_watcher(-1);
	//print_stats();
	printk(KERN_DEBUG "Watcher descarregat amb exit\n");

}

void* read_est(int op, int pid_displayer){
    struct task_struct* task;
    task = find_task_by_pid(pid_displayer);
    printk(KERN_DEBUG "estoy leyendo la operacion: %d con pid: %d\n\n",op,pid_displayer);
    return &((struct my_thread_info*)task->thread_info)->os.est[op];
}
EXPORT_SYMBOL(read_est);


/*No compila por estar estas dos lineas repetidas en los dos ficheros*/
module_init(watcher_init);
module_exit(watcher_exit);
